package oboe.core
{
	import flash.utils.Dictionary;
	import flash.utils.describeType;
	import flash.utils.getQualifiedClassName;

	public class SigSlotCollection
	{
		private var children:Dictionary;

		protected var debugTraces:Boolean;
		
		public static const globalSigIndex:Dictionary = new Dictionary();
		private static var globalSlotIndex:Dictionary = new Dictionary();
		private static var globalPipeIndex:Dictionary = new Dictionary();
		private var slotNames:Dictionary;
		private var slotRegistry:Dictionary;
		private var lastSlotCalled:String;

		public function SigSlotCollection( weakKeys:Boolean=false )
		{	
			this.debugTraces = Oboe.flags["DEBUG"];
			this.children = new Dictionary(weakKeys);
			if( !(Object(this).constructor in globalSigIndex ) ) globalSigIndex[ Object(this).constructor ] = {}; 
		}
		
		protected function getLastSlotCalled():String
		{
			return this.lastSlotCalled;
		}
		
		protected function _getSigSlotChildren():Dictionary
		{
			return this.children;
		}
		
		private function _getFinalClass():Class
		{
			return Object(this).constructor;
		}
		
		private static function setupSlots( child:SigSlotCollection ):void
		{
			var c:Class = child._getFinalClass();
			var slots:Dictionary;
			var pipes:Dictionary;

			var checkPipes:Boolean = child is SigSlot;
			pipes = globalPipeIndex[ c ];
			if( !(slots=globalSlotIndex[ c ]) )
			{
				slots = new Dictionary();
				globalSlotIndex[ c ] = slots;
				var xml:XMLList = describeType( c )..method;
				var len:int = xml.length();
				if( checkPipes )
				{
					pipes = new Dictionary();
					globalPipeIndex[ c ] = pipes;
				}
				for( var i:int=0; i<len; ++i )
				{
					var meta:XMLList = xml[i]..metadata;
					var name:String = String(xml.@name[i]);
					var slotMeta:XMLList = meta.(@name=="slot");
					if( slotMeta.length() )
					{
						slots[ name ] = name;
						var arg:XMLList = slotMeta[0]..arg.(@key=="alias");
						for( var ci:int=0; ci<arg.length(); ++ci )
						{
							slots[ arg[ci].@value.toString() ] = name;
						}
					}
					else if( name.substr(0, 5) == "slot_" )
					{
						slots[ name.substr( 5 ) ] = name;
					}
					if( checkPipes )
					{
						if( meta.(@name=="pipe").length() )
						{
							pipes[ name ] = name;
						}
						else if( name.substr(0, 5) == "pipe_" )
						{
							pipes[ name.substr( 5 ) ] = name;
						}
					}
				}
			}
			child.slotNames = slots;
			child.message("_onInitPipes", pipes);
		}
		
		protected function addChild( child:SigSlotCollection ):void
		{
			if( !this.slotNames )
			{
				SigSlotCollection.setupSlots( this );
			}
			
			if( !child || child == this || child in this.children ) return;
			if( this.debugTraces && child && child.debugTraces ) trace( "adding child", getQualifiedClassName(child), " to ", getQualifiedClassName(this) );
			
			if( !child.slotNames )
			{
				SigSlotCollection.setupSlots( child );
			}
			
			if( !this.slotRegistry )
			{
				this.slotRegistry = new Dictionary( false );
			}
			
			for( var slotName:String in child.slotNames )
			{
				var slotList:Dictionary = this.slotRegistry[ slotName ];
				if( !slotList )
				{
					slotList = new Dictionary( true );
					this.slotRegistry[ slotName ] = slotList;
				}
				slotList[ child ] = child;
			}
			
			this.children[ child ] = child;
		}
		
		protected function removeChild( child:SigSlotCollection ):void
		{
			if( !this.children[ child ] ) return;
			if( this.debugTraces && child.debugTraces ) trace( "removing child", getQualifiedClassName(child), " from ", getQualifiedClassName(this) );
			this.message("onRemoveChild", child);
			delete this.children[ child ];
			
			for( var slotName:String in child.slotNames )
			{
				var slotList:Dictionary = this.slotRegistry[ slotName ];
				if( !slotList )
				{
					continue;
				}
				delete slotList[ child ];
			}
		}
		
		public function message( name:String, ... arguments ):void
		{
			if( !this.slotNames )
			{
				SigSlotCollection.setupSlots( this );
			}
				
			if( name in this.slotNames )
			{
				this[ this.slotNames[ name ] ].apply( this, arguments );
			}
		}
		
		public function messageEx( name:String, arguments:Array ):void
		{
			if( !this.slotNames )
			{
				SigSlotCollection.setupSlots( this );
			}
			
			if( name in this.slotNames )
			{
				this.lastSlotCalled = name;
				this[ this.slotNames[ name ] ].apply( this, arguments );
			}
		}
		
		protected function signalDownPipesAlsoEx( name:String, arguments:Array ):void
		{
			if( this.slotRegistry )
			{
				var listeners:Dictionary = this.slotRegistry[ name ];
				var slot:SigSlotCollection;
				var scratch:Dictionary;
				var k:Object;
				
				if( listeners )
				{
					scratch = new Dictionary();
					
					for( k in listeners )
						scratch[k] = listeners[k];
					
					for each( slot in scratch )
					{
						slot.messageEx( name, arguments );
					}
				}

				scratch = new Dictionary();
				
				for( k in children )
					scratch[k] = children[k];

				for each( var childSSC:SigSlotCollection in scratch )
				{
					if( !(childSSC is SigSlot) ) continue;
					var child:SigSlot = childSSC as SigSlot;
					var func:Function = (child.__getPipes() || {})[ name ];
					if( func != null ) func.apply( child, arguments );
				}
			}			
		}

		protected function signalDownEx( name:String, arguments:Array ):void
		{
			if( this.slotRegistry )
			{
				var listeners:Dictionary = this.slotRegistry[ name ];
				var slot:SigSlotCollection;
				if( !listeners ) return;
				
				for each( slot in listeners )
				{
					slot.messageEx( name, arguments );
				}
			}			
		}
		
		
		protected function signalDown( name:String, ... arguments ):void
		{
			if( this.slotRegistry )
			{
				var listeners:Dictionary = this.slotRegistry[ name ];
				var slot:SigSlotCollection;
				if( !listeners ) return;
				
				for each( slot in listeners )
				{
					slot.messageEx( name, arguments );
				}
			}			
		}
		
		protected function getSlots():Dictionary
		{
			return this.slotNames;
		}
	}
}